package com.ericsson.middleware.ContextBroker;


import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.ws.rs.core.MediaType;

import org.apache.commons.httpclient.HttpStatus;

import com.ericsson.middleware.PropertiesProvider;
import com.ericsson.middleware.slsp.ParkingSpot;
import com.ericsson.middleware.slsp.Record;
import com.ericsson.middleware.utils.JsonUtil;
import com.ericsson.middleware.utils.StringNormalizer;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource.Builder;

public class ContextBrokerClient {

	private final static Logger LOGGER = Logger.getLogger(ContextBrokerClient.class
			.getName());

	public UpdateContextResponse sendUpdateContextSetWithReAttempts(UpdateContext updateContext, int reAttempts) {		
		Map<String,ContextElement> toReuse = new HashMap<String, ContextElement>();	
		for (ContextElement temp : updateContext.getContextElements()) {
			toReuse.put(temp.getId(), temp);
		}
		List<ContextElement> toReAttempt = new ArrayList<ContextElement>();

		int attempts = reAttempts;
		while(attempts >= 0) {
			try {
				LOGGER.log(Level.INFO, ">>>>> UPDATE request: " + JsonUtil.toJson(updateContext));
				UpdateContextResponse response = sendUpdateContext(JsonUtil.toJson(updateContext));
				LOGGER.log(Level.INFO, ">>>>> UPDATE response: " + JsonUtil.toJson(response));
				toReAttempt.clear();
				if (response != null) {
					for (ContextResponse temp : response.getContextResponses()) {
						if (Integer.parseInt(temp.getStatusCode().getCode()) != HttpStatus.SC_OK){
							LOGGER.log(Level.WARNING, "FAILED TO UPDATE " + temp.getContextElement().getId());
							toReAttempt.add(toReuse.get(temp.getContextElement().getId()));
						}
					}
					if (toReAttempt.isEmpty()) {
						LOGGER.log(Level.WARNING, "SUCCESSFULLY UPDATE ");
						return response;
					}
					else {
						updateContext.setContextElements(toReAttempt);
					}
				}				
			}
			catch (Exception e) {					
				LOGGER.log(Level.WARNING, "Exception " + e.getMessage());
			}
			LOGGER.log(Level.WARNING, "unsuccessfull UpdateContext ");
			attempts--;
		}
		return null;

	}

	public UpdateContextResponse sendUpdateContextWithReAttempts(String updateContextJson, int reAttempts) {
		String json = updateContextJson;
		int attempts = reAttempts;
		while(attempts >= 0) {
			try {
				UpdateContextResponse response = sendUpdateContext(json);
				if (response != null && Integer.parseInt(response.getContextResponses().get(0).getStatusCode().getCode()) < HttpStatus.SC_BAD_REQUEST) {
					return response;
				}				
			}
			catch (Exception e) {						
			}
			LOGGER.log(Level.WARNING, "unsuccessfull UpdateContext ");
			attempts--;
		}
		return null;

	}

	public UpdateContextResponse  sendUpdateContext(String updateContextJson) {


		try {
			Client client = Client.create();

			Builder webResource = client
					.resource(PropertiesProvider.getCONTEXT_BROKER_UPDATE_CONTEXT_ADDRESS()).type(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON);

			ClientResponse response = webResource.type(MediaType.APPLICATION_JSON)
					.post(ClientResponse.class, updateContextJson);

			if (response.getStatus() != HttpStatus.SC_OK) {
				LOGGER.log(Level.WARNING, "UpdateContext response: " + response.getStatus());
				throw new RuntimeException("Failed : HTTP error code : "
						+ response.getStatus());
			}

			String responseJson = response.getEntity(String.class);			
			UpdateContextResponse responseList = ((UpdateContextResponse)JsonUtil.fromJson(responseJson, UpdateContextResponse.class));
			return responseList;

		} catch (Exception e) {
			LOGGER.log(Level.WARNING, "UpdateContext could not get response: " + e.getMessage());
		}
		return null;

	}

	public RootSubscribeResponse sendSubscribeContext(String subscribeContextJson) {
		try {
			LOGGER.log(Level.INFO, "Sendind subscribeContext " + subscribeContextJson);
			Client client = Client.create();

			Builder webResource = client
					.resource(PropertiesProvider.getCONTEXT_BROKER_SUBSCRIBE_CONTEXT_ADDRESS()).type(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON);

			ClientResponse response = webResource.type(MediaType.APPLICATION_JSON)
					.post(ClientResponse.class, subscribeContextJson);

			if (response.getStatus() != HttpStatus.SC_OK) {
				throw new RuntimeException("Failed : HTTP error code : "
						+ response.getStatus());
			}

			String responseJson = response.getEntity(String.class);			
			LOGGER.log(Level.INFO, "RESPONSE subscribeContext " + responseJson);
			RootSubscribeResponse responseJava = ((RootSubscribeResponse)JsonUtil.fromJson(responseJson, RootSubscribeResponse.class));
			return responseJava;

		} catch (Exception e) {
			LOGGER.log(Level.WARNING, "subscribeContext could not get response");
		}

		return null;
	}

	public RootUnsubscribeResponse sendUnSubscribeContext(String unsubscribeContextJson) {
		try {
			LOGGER.log(Level.INFO, "Sendind unsubscribeContext " + unsubscribeContextJson);
			Client client = Client.create();

			Builder webResource = client
					.resource(PropertiesProvider.getCONTEXT_BROKER_UNSUBSCRIBE_CONTEXT_ADDRESS()).type(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON);

			ClientResponse response = webResource.type(MediaType.APPLICATION_JSON)
					.post(ClientResponse.class, unsubscribeContextJson);

			if (response.getStatus() != HttpStatus.SC_OK) {
				throw new RuntimeException("Failed : HTTP error code : "
						+ response.getStatus());
			}

			String responseJson = response.getEntity(String.class);			
			LOGGER.log(Level.INFO, "RESPONSE unsubscribeContext " + responseJson);
			RootUnsubscribeResponse responseJava = ((RootUnsubscribeResponse)JsonUtil.fromJson(responseJson, RootUnsubscribeResponse.class));
			return responseJava;

		} catch (Exception e) {
			LOGGER.log(Level.WARNING, "unsubscribeContext could not get response");
		}

		return null;
	}

	public UpdateContext buildUpdateContext(String type, String updateAction, Record record){

		//Send lamp Status to CB
		String builtId = record.getBranchName() + "_"
				+ record.getLuminaireName();

		// we need to mount the position by ourselves (concat Lat and Long)
		Attribute positionAttr = new Attribute();
		positionAttr.setName("position");
		positionAttr.setType("coords");
		positionAttr.setValue(record.getAdditionalProperties().get("Lat").toString() + ", " + record.getAdditionalProperties().get("Long").toString());
		Metadata metadata = new Metadata();
		metadata.setName("location");
		metadata.setType("string");
		metadata.setValue("WSG84");
		List<Metadata> metadatas = new ArrayList<Metadata>();
		metadatas.add(metadata);
		positionAttr.setMetadatas(metadatas);

		// fill context with attributes
		List<Attribute> attributes = new ArrayList<Attribute>();
		for (Entry<String, Object> entry : record.getAdditionalProperties().entrySet()) {
			Attribute newAttr = new Attribute();
			if (!entry.getKey().equals("Lat") && !entry.getKey().equals("Long")) {
				newAttr.setName(entry.getKey().toLowerCase());
				newAttr.setType(PropertiesProvider.getJSON_LAMP_ATTRIBUTES_TYPES_MAP().get(entry.getKey()));
				if (entry.getKey().equals("Status")){				
					if (record.getPWM() > 5) {
						newAttr.setValue(true);
					}
					else {
						newAttr.setValue(false);
					}
				}
				else {
					newAttr.setValue((String) entry.getValue());
				}			
				newAttr.setMetadatas(null);
				attributes.add(newAttr);
			}
		}
		attributes.add(positionAttr);

		List<ContextElement> contextElements = new ArrayList<ContextElement>();
		ContextElement contextElement = new ContextElement();
		contextElement.setId(builtId);
		contextElement.setIsPattern("false");
		contextElement.setType(type);		

		contextElement.setAttributes(attributes);

		UpdateContext updateContext = new UpdateContext();
		updateContext.setUpdateAction(updateAction);

		contextElements.add(contextElement);
		updateContext.setContextElements(contextElements);							

		return updateContext;		
	}

	// not used yet
	public UpdateContext buildUpdateContextSetLamps(String type, String updateAction, List<Record> records){

		UpdateContext updateContext = new UpdateContext();
		List<ContextElement> contextElements = new ArrayList<ContextElement>();
		updateContext.setUpdateAction(updateAction);

		for (Record record : records) {

			//Send lamp Status to CB
			String builtId = record.getBranchName() + "_"
					+ record.getLuminaireName();

			// fill context with attributes
			List<Attribute> attributes = new ArrayList<Attribute>();

			// we need to mount the position by ourselves (concat Lat and Long)
			Attribute positionAttr = new Attribute();
			positionAttr.setName("position");
			positionAttr.setType("coords");
			positionAttr.setValue(record.getAdditionalProperties().get("Lat").toString() + ", " + record.getAdditionalProperties().get("Long").toString());
			Metadata metadata = new Metadata();
			metadata.setName("location");
			metadata.setType("string");
			metadata.setValue("WSG84");
			List<Metadata> metadatas = new ArrayList<Metadata>();
			metadatas.add(metadata);
			positionAttr.setMetadatas(metadatas);

			for (Entry<String, Object> entry : record.getAdditionalProperties().entrySet()) {
				Attribute newAttr = new Attribute();
				if (!entry.getKey().equals("Lat") && !entry.getKey().equals("Long")) {
					newAttr.setName(entry.getKey().toLowerCase());
					newAttr.setType(PropertiesProvider.getJSON_LAMP_ATTRIBUTES_TYPES_MAP().get(entry.getKey()));
					if (entry.getKey().equals("Status")){						
						if (record.getPWM() > 5) {
							newAttr.setValue(true);
						}
						else {
							newAttr.setValue(false);
						}
					}
					else {
						newAttr.setValue((String) entry.getValue());
					}			
					newAttr.setMetadatas(null);
					attributes.add(newAttr);
				}
			}
			attributes.add(positionAttr);


			ContextElement contextElement = new ContextElement();
			contextElement.setId(builtId);
			contextElement.setIsPattern("false");
			contextElement.setType(type);		

			contextElement.setAttributes(attributes);			
			contextElements.add(contextElement);
			updateContext.setContextElements(contextElements);		
		}

		return updateContext;		
	}


	public UpdateContext buildUpdateContext(String type, String updateAction, ParkingSpot record){

		//Send parking spot Status to CB
		String builtId = StringNormalizer.normalize(record.getStreetAddress() + "_"
				+ record.getAddress());

		// we need to mount the position by ourselves (concat Lat and Long)
		Attribute positionAttr = new Attribute();
		positionAttr.setName("position");
		positionAttr.setType("coords");
		positionAttr.setValue(record.getAdditionalProperties().get("Lat").toString() + ", " + record.getAdditionalProperties().get("Lng").toString());
		Metadata metadata = new Metadata();
		metadata.setName("location");
		metadata.setType("string");
		metadata.setValue("WSG84");
		List<Metadata> metadatas = new ArrayList<Metadata>();
		metadatas.add(metadata);
		positionAttr.setMetadatas(metadatas);

		// fill context with attributes
		List<Attribute> attributes = new ArrayList<Attribute>();
		for (Entry<String, Object> entry : record.getAdditionalProperties().entrySet()) {
			Attribute newAttr = new Attribute();

			if (!entry.getKey().equals("Lat") && !entry.getKey().equals("Lng")) {
				newAttr.setName(entry.getKey().toLowerCase());
				newAttr.setType(PropertiesProvider.getJSON_PARKINGSPOT_ATTRIBUTES_TYPES_MAP().get(entry.getKey()));
				if (entry.getKey().equals("Status")) {			
					if (entry.getValue().equals("Ocupado")){
						newAttr.setValue(true);
					}
					else {
						newAttr.setValue(false);
					}				
				}
				else {
					newAttr.setValue((String) entry.getValue());
				}
				newAttr.setMetadatas(null);
				attributes.add(newAttr);
			}
		}
		attributes.add(positionAttr);

		List<ContextElement> contextElements = new ArrayList<ContextElement>();
		ContextElement contextElement = new ContextElement();
		contextElement.setId(builtId);
		contextElement.setIsPattern("false");
		contextElement.setType(type);		

		contextElement.setAttributes(attributes);

		UpdateContext updateContext = new UpdateContext();
		updateContext.setUpdateAction(updateAction);

		contextElements.add(contextElement);
		updateContext.setContextElements(contextElements);							

		return updateContext;		
	}

	// not used yet
	public UpdateContext buildUpdateContextSetParkingSpots(String type, String updateAction, List<ParkingSpot> records){
		UpdateContext updateContext = new UpdateContext();
		updateContext.setUpdateAction(updateAction);
		List<ContextElement> contextElements = new ArrayList<ContextElement>();

		for (ParkingSpot record : records) {
			//Send parking spot Status to CB
			String builtId = StringNormalizer.normalize(record.getStreetAddress() + "_"
					+ record.getAddress());

			// we need to mount the position by ourselves (concat Lat and Long)
			Attribute positionAttr = new Attribute();
			positionAttr.setName("position");
			positionAttr.setType("coords");
			positionAttr.setValue(record.getAdditionalProperties().get("Lat").toString() + ", " + record.getAdditionalProperties().get("Lng").toString());
			Metadata metadata = new Metadata();
			metadata.setName("location");
			metadata.setType("string");
			metadata.setValue("WSG84");
			List<Metadata> metadatas = new ArrayList<Metadata>();
			metadatas.add(metadata);
			positionAttr.setMetadatas(metadatas);

			// fill context with attributes
			List<Attribute> attributes = new ArrayList<Attribute>();
			for (Entry<String, Object> entry : record.getAdditionalProperties().entrySet()) {
				Attribute newAttr = new Attribute();

				if (!entry.getKey().equals("Lat") && !entry.getKey().equals("Lng")) {
					newAttr.setName(entry.getKey().toLowerCase());
					newAttr.setType(PropertiesProvider.getJSON_PARKINGSPOT_ATTRIBUTES_TYPES_MAP().get(entry.getKey()));
					if (entry.getKey().equals("Status")) {			
						if (entry.getValue().equals("Ocupado")){
							newAttr.setValue(true);
						}
						else {
							newAttr.setValue(false);
						}				
					}
					else {
						newAttr.setValue((String) entry.getValue());
					}
					newAttr.setMetadatas(null);
					attributes.add(newAttr);
				}
			}
			attributes.add(positionAttr);

			ContextElement contextElement = new ContextElement();
			contextElement.setId(builtId);
			contextElement.setIsPattern("false");
			contextElement.setType(type);		

			contextElement.setAttributes(attributes);


			contextElements.add(contextElement);
			updateContext.setContextElements(contextElements);	
		}

		return updateContext;		
	}

	public SubscribeContext buildSubscribeContext(String pattern, String type){

		SubscribeContext subscribeContext = new SubscribeContext(); 

		subscribeContext.setDuration(PropertiesProvider.getCONTEXT_BROKER_SUBSCRIBE_DURATION()); // P1M means one month
		subscribeContext.setReference(PropertiesProvider.getMIDDLEWARE_REFERENCE_ADDRESS());		
		subscribeContext.setThrottling(PropertiesProvider.getCONTEXT_BROKER_SUBSCRIBE_THROTTLING()); // PT15S means 15 seconds

		List<Entity> entities = new ArrayList<Entity>();
		Entity entity = new Entity();
		entity.setType(type);
		entity.setId(pattern + ".*");
		entity.setIsPattern("true");
		entities.add(entity);
		subscribeContext.setEntities(entities);

		List<String> attributes = new ArrayList<String>();
		for (String attr : PropertiesProvider.getONCHANGE_SUBSCRIBE_RESPONSE_ATTRIBUTES()) {
			attributes.add(attr.toLowerCase());
		}
		//attributes.add("PWM");
		subscribeContext.setAttributes(attributes);

		List<NotifyCondition> notifyConditions = new ArrayList<NotifyCondition>();
		NotifyCondition notifyCondition = new NotifyCondition();
		notifyCondition.setType("ONCHANGE");
		List<String> condValues = new ArrayList<String>();
		for (String attr : PropertiesProvider.getONCHANGE_ATTRIBUTES_TYPE()) {
			if (!attr.equals("")) {
				condValues.add(attr.toLowerCase());
			}
		}
		//condValues.add("pwm");
		notifyCondition.setCondValues(condValues);
		notifyConditions.add(notifyCondition);
		subscribeContext.setNotifyConditions(notifyConditions );

		return subscribeContext;		
	}
}